# exception & assert
# 错误和异常
一般程序的错误包括至少两种,一种是语法错误,一种是异常。
语法错误是指代码不符合 Python 的语法规范,程序会报错invalid syntax。
而异常则是指程序的语法正确,也可以被执行,但在执行过程中遇到了错误,抛出了异常。
- 一个 try 语句可能包含多个except子句,分别来处理不同的特定的异常。最多只有一个分支会被执行。
- 一个except子句可以同时处理多个异常,这些异常将被放在一个括号里成为一个元组
- try except 语句一个可选的else子句,如果使用这个子句,那么必须放在所有的except子句之后。这个子句将在try子句没有发生任何异常的时候执行。
- try except 语句还有另外一个可选的finally子句,它定义了无论在任何情况下都会执行的清理行为。
当我们不确定某段代码能否执行成功,往往这个地方就需要使用异常处理
异常不要滥用,有的地方用流程控制即可
import sys
try:
f = open('file.txt', 'r')
.... # some data processing
except OSError as err:
print('OS error: {}'.format(err))
except:
print('Unexpected error:', sys.exc_info()[0])
else: # 没有发生任何异常
pass
finally: # 清理行为,无论如何都会执行
f.close()
# 自定义异常
- python允许用户自定义异常,直接间接继承自 Exception 类即可。
- 使用raise抛出一个指定的异常(Exception 的子类)
class MyInputError(Exception):
"""Exception raised when there're errors in input"""
def __init__(self, value): # 自定义异常类型的初始化
self.value = value
def __str__(self): # 自定义异常类型的string表达形式
return ("{} is invalid input".format(repr(self.value)))
try:
raise MyInputError(1) # 抛出MyInputError这个异常
except MyInputError as err:
print('error: {}'.format(err))
# 异常响应链- raise from
在设计代码的时候,对于在except块中使用raise语句的情况,大家应该特别小心。大部分情况下,这种raise语句都应该改为raise from。也就是说,我们应该采用下面这种风格:
try:
...
except SomeException as e:
raise DifferentException() from e
这么做的原因在于我们需要显式将异常产生的原因串联起来。也就是说,Different Exception是直接响应SomeException。这两个异常间的关系会在traceback回溯中显式给出。
如果采用下面这种风格,还是可以得到异常链。但是通常这么做不能明确表达出异常链是程序员有意为之,还是由于无法预见的编程错误而产生的:
try:
...
except SomeException:
raise DifferentException()
当使用raise from语句时,就需明确表达出你希望引发第二个异常的意图。示例如下:
>>> def fun2():
... try:
... int('N/A')
... except ValueError as e:
... raise RuntimeError('A parsing error occurred') from e
...
>>> fun2()
Traceback (most recent call last):
File "<stdin>", line 3, in fun2
ValueError: invalid literal for int() with base 10: 'N/A'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in fun2
RuntimeError: A parsing error occurred
当在except语句块中引发另一个异常时,会产生异常链的隐式形式,但是会产生上面提到的问题-即不知道是有意抛出还是未知的编程错误:
>>> def example2():
... try:
... int('N/A')
... except ValueError as e:
... print("Couldn't parse:", err)
...
>>>
>>> example2()
Traceback (most recent call last):
File "<stdin>", line 3, in example2
ValueError: invalid literal for int() with base 10: 'N/A'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in example2
NameError: global name 'err' is not defined
raise form None则会隐藏被触发的异常。
>>> def fun2():
... try:
... int('N/A')
... except ValueError as e:
... raise RuntimeError('A parsing error occurred') from None
...
>>> fun2()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in fun2
RuntimeError: A parsing error occurred
# __cause__
可以根据Exception的__cause__属性来获取被触发的原因,其指向了另一个导致了当前正在处理的Exception的Exception对象,在调用链中会被自动设置:
def divide(x, y):
try:
result = x / y
except ZeroDivisionError as e:
raise ValueError('Invalid inputs') from e
else:
return result
try:
divide(5, 0)
except ValueError as e:
print("Cause of error:", e.__cause__)
print(type(e.__cause__))
# Cause of error: division by zero
# <class 'ZeroDivisionError'>
# assert
assert 在python中是一个关键字,用于判断一个表达式,在表达式为False的时候抛出AssertionError异常, 后接optional的message, 例如:
assert 1 != 1, '1 != 1 is not True'
从某种程度上来说其和exception有点类似,但是assert的使用场景和exception是不同的, 这需要很多的工程经验以及一定的代码美感才能做到,这里不展开讲。
需要注意:assert语句在python执行时如果开启-O 是会被优化掉的-直接忽略.
个人认为assert的使用应该是,有没有assert程序都能够正常运行,但有了assert可以使我们的代码后期维护更加方便